/*
* EuroCarbDB, a framework for carbohydrate bioinformatics
*
* Copyright (c) 2006-2009, Eurocarb project, or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
* A copy of this license accompanies this distribution in the file LICENSE.txt.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* Last commit: $Rev: 1573 $ by $Author: glycoslave $ on $Date:: 2009-07-24 #$
*/
package org.eurocarbdb.action.core;
// stdlib imports
import java.util.*;
import java.math.BigDecimal;
// 3rd party imports
import org.apache.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.HibernateException;
import org.hibernate.ScrollableResults;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.criterion.ProjectionList;
import org.hibernate.criterion.Subqueries;
// eurocarb imports
import org.eurocarbdb.dataaccess.core.*;
import org.eurocarbdb.dataaccess.core.seq.*;
import org.eurocarbdb.action.EurocarbAction;
import org.eurocarbdb.action.BrowseAction;
import org.eurocarbdb.dataaccess.EntityManager;
import org.eurocarbdb.dataaccess.HibernateEntityManager;
import org.eurocarbdb.dataaccess.indexes.*;
import org.eurocarbdb.sugar.Sugar;
import org.eurocarbdb.sugar.SugarSequence;
import org.eurocarbdb.application.glycanbuilder.Glycan;
import org.eurocarbdb.dataaccess.SavedGlycanSequenceSearch;
// import org.eurocarbdb.dataaccess.core.seq.SavedGlycanSubstructureSearch;
// static imports
import static org.eurocarbdb.util.StringUtils.join;
import static org.eurocarbdb.dataaccess.Eurocarb.getEntityManager;
import static org.eurocarbdb.dataaccess.core.seq.SubstructureQuery.Option.*;
/* class SearchGlycanSequence *//****************************************
*
* Finds a Set of {@link GlycanSequence} objects satisfying a given
* set of query predicates. Matched sequences are wrapped together
* with their query predicates in a {@link SavedGlycanSequenceSearch} object.
*
* @see SavedGlycanSequenceSearch
* @author mjh
* @author hirenj
* @version $Rev: 1573 $
*/
public class SearchGlycanSequence extends BrowseAction<GlycanSequence>
{
//~~~~~~~~~~~~~~~~~~~~~~ STATIC FIELDS ~~~~~~~~~~~~~~~~~~~~~~~~~~
/** Logging handle. */
static final Logger log = Logger.getLogger( SearchGlycanSequence.class );
//~~~~~~~~~~~~~~~~~~~~~~~~~~ FIELDS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
private String taxonomyName = null;
private String tissueName = null;
private String diseaseName = null;
private String perturbationName = null;
private double lowMass = -1,
highMass = -1,
exactMass = -1,
exactMassTolerance = -1;
private boolean useAvgMass = false;
private boolean useAvgMassGiven = false;
private String sequenceGWS;
// Position to search for the sequence
private String sequencePosition = null;
// Stores the input glycan ID that we wish to search for
private int glycanId = -1;
private boolean validated = false;
private boolean isNewQuery = false;
private List<SavedGlycanSequenceSearch> queryHistory
= new java.util.ArrayList<SavedGlycanSequenceSearch>();
private List<SavedGlycanSequenceSearch> additionalQueries
= new java.util.ArrayList<SavedGlycanSequenceSearch>();
private SavedGlycanSequenceSearch currentSearch;
private int[] historicalQueriesToRun = {};
private int[] historicalQueriesToRefine = {};
/** The {@link List} of {@link Index}es supported by this Action. */
public static final List<Index<GlycanSequence>> indexes = Arrays.asList(
new IndexByContributedDate<GlycanSequence>(),
new IndexByContributorName<GlycanSequence>(),
new IndexByMostEvidence<GlycanSequence>(),
new IndexByResidueCount<GlycanSequence>()
);
//~~~~~~~~~~~~~~~~~~~~~~~~~ METHODS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~~ criteria creation & access methods ~~~~~
public Criteria createCriteria()
{
DetachedCriteria crit = createSerializableCriteria();
crit.setProjection(
Projections.distinct(
Projections.property("glycanSequenceId")));
Criteria criteria = getEntityManager().createQuery(GlycanSequence.class);
criteria.add( Subqueries.propertyIn("glycanSequenceId",crit) );
if( getIndex() != null )
{
getIndex().apply(criteria);
}
return criteria;
}
private DetachedCriteria createSerializableCriteria()
{
// create base criteria
log.debug("creating GlycanSequence criteria");
DetachedCriteria criteria;
criteria = DetachedCriteria.forClass( GlycanSequence.class );
// create biological contexts criteria
DetachedCriteria bc_criteria = null;
DetachedCriteria tax_criteria = null;
DetachedCriteria tissue_criteria = null;
DetachedCriteria disease_criteria = null;
DetachedCriteria perturbation_criteria = null;
if( taxonomyName!=null || tissueName!=null || diseaseName!=null || perturbationName != null ) {
isNewQuery = true;
log.debug("creating Biological context criteria");
bc_criteria = criteria.createCriteria("glycanContexts")
.createCriteria("biologicalContext", "bc");
// add taxonomy criteria
if( taxonomyName!=null ) {
log.debug("adding taxonomy query predicates for input string '" + taxonomyName + "'");
tax_criteria = bc_criteria.createCriteria("taxonomy", "taxa")
.createCriteria("taxonomySupertypes", "supertax")
.add( Restrictions.ilike( "taxon", taxonomyName, MatchMode.EXACT ) );
}
// add tissue criteria
if( tissueName!=null ) {
log.debug("adding tissue query predicates for input string '" + tissueName + "'");
tissue_criteria = bc_criteria.createCriteria("tissueTaxonomy", "ttax")
.add( Restrictions.ilike("tissueTaxon", tissueName, MatchMode.EXACT ) );
}
// add disease criteria
if( diseaseName!=null ) {
log.debug("adding disease query criteria for input string '" + diseaseName + "'");
disease_criteria = bc_criteria.createCriteria("diseaseContexts")
.createCriteria("disease", "dis")
.add( Restrictions.ilike("diseaseName", diseaseName, MatchMode.EXACT ) );
}
if ( perturbationName!=null ) {
log.debug("adding perturbation query criteria for input string '" + perturbationName + "'");
perturbation_criteria = bc_criteria.createCriteria("perturbationContexts")
.createCriteria("perturbation", "per")
.add( Restrictions.ilike("perturbationName", perturbationName, MatchMode.EXACT ) );
}
}
// add mass criteria
boolean mass_query_is_given = false;
boolean params_are_ok = false;
if ( exactMass > 0 && exactMassTolerance > 0 )
{
isNewQuery = true;
mass_query_is_given = true;
lowMass = exactMass - exactMassTolerance;
highMass = exactMass + exactMassTolerance;
log.debug( "adding predicates for exactMass=" + exactMass + " Da +/- " + exactMassTolerance + " Da (ie: " + lowMass + "-" + highMass + " Da)" );
params_are_ok = true;
}
else if ( lowMass > 0 && highMass > 0 )
{
isNewQuery = true;
mass_query_is_given = true;
exactMass = -1;
exactMassTolerance = -1;
log.debug( "adding predicates for mass range=(" + lowMass + ".." + highMass + " Da)" );
params_are_ok = true;
}
if ( mass_query_is_given )
{
if ( params_are_ok )
{
isNewQuery = true;
String property = useAvgMass ? "massAverage" : "massMonoisotopic";
criteria.add( Restrictions.between(property, new BigDecimal(lowMass), new BigDecimal(highMass) ) );
}
else
{
String msg = "Insufficient mass parameters given, either "
+ "provide an exactMass + exactMassTolerence + useAvgMass preference, "
+ "or provide a lowMass + highMass + useAvgMass preference";
addActionError( msg );
log.info( msg );
}
}
Glycan glycan = null;
if ( sequenceGWS != null )
{
glycan = Glycan.fromString(sequenceGWS);
glycan.removeReducingEndModification();
if (glycan.isEmpty())
{
glycan = null;
sequenceGWS = null;
}
}
if ( glycan != null )
{
isNewQuery = true;
// search structure in DB
String glycoct = glycan.toGlycoCTCondensed();
SugarSequence seq = new SugarSequence( glycoct );
SubstructureQuery query = new SubstructureQuery( seq );
if ( sequencePosition != null )
{
if ( sequencePosition.equals("Core") || sequencePosition.equals("Core + Terminii") )
query.setOption( Must_Include_Reducing_Terminus );
if ( sequencePosition.equals("Terminii") || sequencePosition.equals("Core + Terminii") )
query.setOption( Must_Include_All_Non_Reducing_Terminii );
}
criteria.add( query.getQueryCriterion() );
}
if ( this.additionalQueries.size() > 1 )
{
isNewQuery = true;
}
for ( SavedGlycanSequenceSearch oldQuery : this.additionalQueries )
{
DetachedCriteria oldCriteria = oldQuery.getQueryCriteria();
criteria.add( Subqueries.propertyIn("glycanSequenceId", oldCriteria) );
oldCriteria.setProjection(
Projections.distinct(
Projections.property("glycanSequenceId")));
this.currentSearch = oldQuery;
}
return criteria;
}
//~~~~~~~~~~~~ Query history methods ~~~~~~~~~~~~~~~~~~~~~~~~~~~
public List<SavedGlycanSequenceSearch> getQueryHistory()
{
return queryHistory;
}
public void setQueryHistory(List<SavedGlycanSequenceSearch> history)
{
this.queryHistory = history;
}
public int[] getHistoricalQueriesToRefine()
{
return this.historicalQueriesToRefine;
}
public void setHistoricalQueriesToRefine( int[] ids )
{
this.historicalQueriesToRefine = ids;
for ( int index : ids )
{
if (this.getQueryHistory().size() > index)
{
this.additionalQueries.add(this.getQueryHistory().get(index));
}
}
}
public int[] getHistoricalQueriesToRun()
{
return this.historicalQueriesToRun;
}
public void setHistoricalQueriesToRun( int[] ids )
{
this.historicalQueriesToRun = ids;
for( int index : ids )
{
if ( this.getQueryHistory().size() > index )
{
this.additionalQueries.add( this.getQueryHistory().get(index) );
}
}
}
public List<SavedGlycanSequenceSearch> getAdditionalQueries()
{
return this.additionalQueries;
}
public SavedGlycanSequenceSearch getCurrentSearch()
{
return this.currentSearch;
}
//~~~~~~~~~~~~ query predicate creation methods ~~~~~~~~~~~~~~~
public String getSequenceGWS()
{
return sequenceGWS;
}
public void setSequenceGWS(String str)
{
sequenceGWS = str;
}
public boolean isSearchCore()
{
return false;
}
public boolean isSearchTerminal()
{
return false;
}
// taxonomy query predicates //
public void setTaxonomyName( String namestring )
{
if( namestring!=null && namestring.trim().length()>0 )
taxonomyName = namestring.trim();
else
taxonomyName = null;
}
public String getTaxonomyName()
{
return taxonomyName;
}
public void setTissueName( String namestring )
{
if( namestring!=null && namestring.trim().length()>0 )
tissueName = namestring.trim();
else
tissueName = null;
}
public String getTissueName()
{
return tissueName;
}
public void setDiseaseName( String namestring )
{
if( namestring!=null && namestring.trim().length()>0 )
diseaseName = namestring.trim();
else
diseaseName = null;
}
public String getDiseaseName()
{
return diseaseName;
}
/**
* Get accessor for perturbationName
* Perturbation query string accessor
*/
public String getPerturbationName()
{
return this.perturbationName;
}
/**
* Set accessor for perturbationName
* @param perturbationName Data to set
* Perturbation query string accessor
*/
public void setPerturbationName(String namestring)
{
if( namestring!=null && namestring.trim().length()>0 )
perturbationName = namestring.trim();
else
perturbationName = null;
}
// mass query predicates //
public void setAvgMass( boolean b )
{
useAvgMass = b;
useAvgMassGiven = true;
}
public boolean getAvgMass()
{
return useAvgMass;
}
public void setMonoisoMass( boolean b )
{
useAvgMass = ! b;
useAvgMassGiven = true;
}
public void setDiscreteMass( double mass )
{
exactMass = mass;
}
public double getDiscreteMass()
{
return exactMass;
}
public void setDiscreteMassTolerance( double tolerance )
{
exactMassTolerance = Math.abs( tolerance );
}
public double getDiscreteMassTolerance()
{
return exactMassTolerance;
}
public void setLowMass( double mass )
{
lowMass = mass;
}
public double getLowMass()
{
return lowMass;
}
public void setHighMass( double mass )
{
highMass = mass;
}
public double getHighMass()
{
return highMass;
}
/**
* Get accessor for sequencePosition
* Position to search for the sequence
*/
public String getSequencePosition()
{
return this.sequencePosition;
}
/**
* Set accessor for sequencePosition
* @param sequencePosition Data to set
* Position to search for the sequence
*/
public void setSequencePosition(String sequencePosition)
{
this.sequencePosition = sequencePosition;
}
/**
* Get accessor for glycanId
* Stores the input glycan ID that we wish to search for
*/
public int getGlycanId()
{
return this.glycanId;
}
/**
* Set accessor for glycanId
* @param glycanId Data to set
* Stores the input glycan ID that we wish to search for
*/
public void setGlycanId(int glycanId)
{
this.glycanId = glycanId;
}
public String[] getQueryDescription()
{
ArrayList<String> descriptions = new ArrayList<String>();
if ( getTaxonomyName() != null )
{
descriptions.add( "taxonomy equals " + getTaxonomyName() );
}
if ( getTissueName() != null )
{
descriptions.add( "tissue equals " + getTissueName() );
}
if ( getDiseaseName() != null )
{
descriptions.add( "disease equals " + getDiseaseName() );
}
if ( getPerturbationName() != null )
{
descriptions.add( "perturbation equals " + getPerturbationName() );
}
if ( exactMass > 0 || lowMass > 0 )
{
String massType = useAvgMass ? "average" : "monoisotopic";
if ( exactMass > 0 )
{
descriptions.add( massType
+ " mass equals "
+ exactMass
+ " ± "
+ exactMassTolerance
+ " Da"
);
}
else
{
descriptions.add( massType
+ " between "
+ lowMass
+ " and "
+ highMass
+ " Da"
);
}
}
if ( getSequenceGWS() != null )
{
descriptions.add(
" substructure is found"
+ ((sequencePosition != null && ! "Anywhere".equals( sequencePosition ) )
? " at " + sequencePosition.toLowerCase()
: "" )
);
}
for ( SavedGlycanSequenceSearch query : this.additionalQueries )
{
if ( query.description != null )
{
descriptions.add( query.description );
}
}
return descriptions.toArray( new String[0] );
}
//~~~~~~~~~~~~~~~~~ composition query options ~~~~~~~~~~~~~~~~~~~
public void setExactComp( String s ) { /* TODO */ }
public void setMinComp( String s ) { /* TODO */ }
public void setMaxComp( String s ) { /* TODO */ }
//~~~~~~~~~~~~~~~~~~~~~~~~ indexing ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/** Default index is the first index in the list of indexes */
@Override
public Index<GlycanSequence> getDefaultIndex()
{
return indexes.get( 0 );
}
@Override
public List<Index<GlycanSequence>> getIndexes()
{
return indexes;
}
public final Class<GlycanSequence> getIndexableType()
{
return GlycanSequence.class;
}
//~~~~~~~~~~~~~~~~~~~~~~~ validation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public void validate()
{
// return unless we're processing input params
if ( getParameters() == null || getParameters().size() == 0 )
return;
// only perform validation once, per request
if ( validated ) return;
validated = true;
}
public List<GlycanSequence> getQueryResults()
{
Criteria query = createCriteria();
log.info( "Performing query: " + query.toString() );
setMessage(query.toString());
ScrollableResults scroll = null;
try
{
scroll = query.scroll();
scroll.last();
setTotalResults(scroll.getRowNumber()+1);
int count = getTotalResults();
int first = getOffset();
int max = getMaxResults();
if ( first > 0 )
{
query.setFirstResult(first);
}
if ( max > 0 )
{
query.setMaxResults(max);
}
List<GlycanSequence> ret = (List<GlycanSequence>) query.list();
log.debug( "query executed ok, results count=" + ret.size() );
return ret;
}
catch ( HibernateException e )
{
log.warn( "Caught "
+ e.getClass().getName()
+ " performing query:", e
);
return Collections.emptyList();
}
finally
{
if ( scroll != null )
scroll.close();
}
}
/* execute *///************************************************
@SuppressWarnings("unchecked")
public String execute()
{
if ( getParameters() == null || getParameters().size() == 0 )
{
log.debug("no input params given, returning 'input' view");
return "input";
}
if ( getParameters().get("historicalQueriesToRefine") != null) {
log.debug("refining an existing query, returning 'input' view");
return "input";
}
if ( glycanId > 0 )
{
return "show";
}
validate();
setResults( getQueryResults() );
if ( isNewQuery )
{
SavedGlycanSequenceSearch savedSearch = new SavedGlycanSequenceSearch();
savedSearch.queryCriteria = createSerializableCriteria();
savedSearch.description = join(" AND ", getQueryDescription());
savedSearch.resultCount = getTotalResults();
savedSearch.sequence = sequenceGWS;
List<SavedGlycanSequenceSearch> history = this.getQueryHistory();
history.add(savedSearch);
this.setQueryHistory(history);
this.currentSearch = savedSearch;
}
return hasActionErrors() ? "input" : "success";
}
} // end class